(function ( exports ) {


    //region Relationship

    /**
     * Relationship
     * @param {string} schemaName Schema name
     * @prop {string} schemaName Schema name
     * @prop {string} primaryEntityRole Primary Entity Role
     * @memberOf COSA
     * @constructor Relationship
     */
    function Relationship ( schemaName ) {
        COSA.Xrm2011ContractsValueBase.call(this);

        this.schemaName = schemaName;
        this.primaryEntityRole = null;
    }

    COSA.Util.deriveOverride(COSA.Xrm2011ContractsValueBase, Relationship, {
        type: 'Relationship',
        requireTypeDef: true,
        validate: function () {
            (new COSA.ValueChecker(this.schemaName))
                .defined()
                .typeOf('string');
        },
        getTemplate: function ( sc, tgr ) {
            return [{
                tag: tgr.tag('PrimaryEntityRole'),
                attr: { 'i:nil':'true' }
            }, {
                tag: tgr.tag('SchemaName'),
                val: this.schemaName
            }];
        }
    });

    //endregion Relationship

    //region BusinessEntity

    /**
     * CRM Entity
     * @param {string} logicalName Entity logical name
     * @param {string} id Guid-like string as Entity unique identifier
     * @param {object} attributes Entity attributes
     * @prop {string} id Entity unique identifier
     * @prop {string} logicalName Entity logical name
     * @prop {object} attributes Entity attributes
     * @memberOf COSA
     * @constructor BusinessEntity
     */
    function BusinessEntity ( logicalName, id, attributes ) {
        COSA.Xrm2011ContractsValueBase.call(this);

        this.id = id === null ? '00000000-0000-0000-0000-000000000000' : id;
        this.logicalName = logicalName;
        this.attributes = typeof attributes != 'undefined' ? attributes : {};
    }

    COSA.Util.deriveOverride(COSA.Xrm2011ContractsValueBase, BusinessEntity, {
        type:'Entity',
        requireTypeDef: true,
        getTemplate: function ( sc, tgr ) {
            var encoded = this.getEncoded();

            return [{
                tag: tgr.tag('Attributes'),
                nodes: (new COSA.KeyValuePairCollection(this.attributes)).serialize(sc)
            }, {
                tag: tgr.tag('EntityState'),
                attr: { 'i:nil': 'true' }
            }, {
                tag: tgr.tag('FormattedValues')
            }, {
                tag: tgr.tag('Id'),
                val: encoded.id
            }, {
                tag: tgr.tag('LogicalName'),
                val: encoded.logicalName
            }, {
                tag: tgr.tag('RelatedEntities')
            }];
        },
        getEncoded: function () {
            var enc = new COSA.XrmEncoder();
            return {
                id: enc.encodeGuid(this.id),
                logicalName: enc.encodeString(this.logicalName)
            };
        },
        validate: function () {
            (new COSA.ValueChecker(this.id))
                .defined()
                .guid();

            (new COSA.ValueChecker(this.logicalName))
                .defined()
                .typeOf('string');

            (new COSA.ValueChecker(this.attributes))
                .defined()
                .typeOf('object');
        },
        deserialize: function ( node ) {
            var prts = node.namedNodes(), formattedAttrs, attrArr;

            this.id = prts.Id.text();
            this.logicalName = prts.LogicalName.text();

            if ( typeof prts.FormattedValues != 'undefined' ) {
                formattedAttrs = prts.FormattedValues.keyValueCollection();
            }

            if ( typeof prts.Attributes != 'undefined' ) {
                attrArr = prts.Attributes.keyValueCollection();
            }

            var attrType, tmp;
            for ( var attr in attrArr ) {
                attrType = attrArr[attr].attr('i:type').split(':')[1];
                switch ( attrType ) {
                    case 'decimal':
                        this.attributes[attr] = new COSA.XrmDecimal(parseFloat(attrArr[attr].text()));
                        this.attributes[attr].formattedValue = formattedAttrs[attr].text();
                        break;
                    case 'double':
                        this.attributes[attr] = new COSA.XrmDouble(parseFloat(attrArr[attr].text()));
                        this.attributes[attr].formattedValue = formattedAttrs[attr].text();
                        break;
                    case 'Money':
                        this.attributes[attr] = new COSA.XrmMoney(
                            parseFloat(attrArr[attr].text()),
                            formattedAttrs[attr].text()
                        );
                        break;
                    case 'int':
                        this.attributes[attr] = new COSA.XrmWholeNumber(
                            parseInt(attrArr[attr].text())
                        );
                        break;
                    case 'string':
                        this.attributes[attr] = new COSA.XrmString(attrArr[attr].text());
                        break;
                    case 'EntityReference':
                        tmp = attrArr[attr].namedNodes();

                        this.attributes[attr] = new COSA.EntityReference(
                            tmp.LogicalName.text(),
                            tmp.Id.text()
                        );
                        this.attributes[attr].name = tmp.Name.text();
                        break;
                    case 'boolean':
                        this.attributes[attr] = new COSA.XrmBoolean(attrArr[attr].text() == 'true');
                        this.attributes[attr].formattedValue = formattedAttrs[attr].text();
                        break;
                    case 'guid':
                        this.attributes[attr] = new COSA.XrmGuid(attrArr[attr].text());
                        break;
                    case 'OptionSetValue':
                        this.attributes[attr] = new COSA.OptionSetValue(
                            parseInt(attrArr[attr].text()),
                            formattedAttrs[attr].text()
                        );
                        break;
                    case 'dateTime':
                        this.attributes[attr] = new COSA.XrmDateTime(
                            new Date(attrArr[attr].text()),
                            formattedAttrs[attr].text()
                        );
                        break;
                    case 'EntityCollection':
                        this.attributes[attr] = (new COSA.EntityCollection())
                            .deserialize(attrArr[attr]);
                        break;
                }
            }

            return this;
        }
    });

    //endregion BusinessEntity

    //region EntityReference

    /**
     * EntityReference
     * @param {string} logicalName Entity Logical Name
     * @param {string} id Entity unique identifier (GUID)
     * @prop {string} logicalName Entity Logical Name
     * @prop {string} id Entity unique identifier (GUID)
     * @prop {string} name Formatted value
     * @memberOf COSA
     * @constructor EntityReference
     */
    function EntityReference ( logicalName, id ) {
        COSA.Xrm2011ContractsValueBase.call(this);

        this.id = id;
        this.logicalName = logicalName;
        this.name = '';
    }

    COSA.Util.deriveOverride(COSA.Xrm2011ContractsValueBase, EntityReference, {
        type: 'EntityReference',
        requireTypeDef: true,
        getTemplate: function ( sc, tgr ) {
            var encoded = this.getEncoded();
            return [{
                tag: tgr.tag('Id'),
                val: encoded.id
            }, {
                tag: tgr.tag('LogicalName'),
                val: encoded.logicalName
            }, {
                tag: tgr.tag('Name'),
                attr: { 'i:nil':'true' }
            }];
        },
        deserialize: function ( node ) {
            var namedCh = node.namedNodes();

            this.id = namedCh.Id.text();
            this.logicalName = namedCh.LogicalName.text();
            this.name = namedCh.Name.text();

            return this;
        },
        getEncoded: function () {
            var enc = new COSA.XrmEncoder();
            return {
                id: enc.encodeGuid(this.id),
                logicalName: enc.encodeString(this.logicalName)
            };
        },
        validate: function () {
            (new COSA.ValueChecker(this.id))
                .defined()
                .typeOf('string')
                .guid();

            (new COSA.ValueChecker(this.logicalName))
                .defined()
                .typeOf('string');
        }
    });

    //endregion EntityReference

    //region KeyValuePair

    function KeyValuePair ( key, value ) {
        COSA.KeyValuePairBase.call(this, key, value);
    }

    COSA.Util.deriveOverride(COSA.KeyValuePairBase, KeyValuePair, {
        namespace: 'http://schemas.microsoft.com/xrm/2011/Contracts',
        type: 'KeyValuePairOfstringanyType',
        getTemplate: function ( sc, ltr ) {
            var node = { tag: ltr.tag(this.type) },
                kvpair = new COSA.KeyValuePairBase(this.key, this.value);

            node.nodes = kvpair.serialize(sc);

            return node;
        },
        validate: function () {
            (new COSA.ValueChecker(this.valuePair))
                .defined()
                .instanceOf(COSA.KeyValuePairBase)
                .valid();
        }
    });

    //endregion KeyValuePair

    exports.Relationship = Relationship;
    exports.BusinessEntity = BusinessEntity;
    exports.EntityReference = EntityReference;
    exports.KeyValuePair = KeyValuePair;
})(COSA);
